import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';

import 'common.dart';

typedef WindowSizeChangeCallback = void Function(double width, double height);

/// 屏幕自适应工具，用于根据不同屏幕尺寸赋予不同属性值
///
/// 1. 组件尺寸属性根据设计稿尺寸缩放功能：默认设计稿尺寸可根据需求通过 [designSize]
///    修改，通过对 [num] 的扩展方法 ad2h、ad2w 可实现相应的屏幕尺寸自适应
///
/// 2. 没有线形变化能力的其他组件属性（如 [BoxShape] 、[Alignment] 等），可以通过
///    设置屏幕打断点后，根据当前屏幕尺寸所属打断点尺寸响应式地设置组件属性，实现不
///    同屏幕尺寸下，组件属性的响应式变化，通过对各个属性的 [adapt] 拓展方法实现
class Adapter extends InheritedWidget {
  static bool _inited = false;

  /// 尺寸缩放比例
  static double scale = 1;

  /// 应用宽高
  static Size _windowSize = Size.zero;

  /// 应用宽高的最小尺寸，用于计算屏幕缩放比例
  static double _windowMinSize = 0;

  /// 当前屏幕打断点索引
  static int _breakPointIndex = -1;

  /// 在当前屏幕打断点尺寸段的进度
  static double _breakPointRatio = 0.0;

  /// 设计稿尺寸
  static Size _designSize = const Size(1084, 1920);

  /// 打断点尺寸，先按宽度升序，同宽度下再按高度升序排列
  /// 例如 [Size(720, 1260), Size(1080, 1920), Size(1440, 1080), Size(1440, 1920)]
  static List<Size> _breakPoints = [];

  // 区分 PC 与手机屏幕尺寸打断点
  static Size deviceTypePointer = const Size(1440, 2600);

  /// 屏幕尺寸变化监听回调
  static final List<WindowSizeChangeCallback> _sizeCbs = [];

  const Adapter({super.key, required super.child});

  /// 设置设计稿尺寸
  static set designSize(Size size) {
    _designSize = size / scale.toDouble();
  }

  /// 屏幕尺寸更新间隔时长
  static Duration updateInterval = Durations.medium1;

  /// 更新窗口尺寸
  static void refreshSize({double? height, double? width}) {
    var size = MediaQueryData.fromView(
            WidgetsBinding.instance.platformDispatcher.views.first)
        .size;

    Adapter._windowSize = size;
    Adapter._windowMinSize = min(size.width, size.height);
  }

  /// 设置屏幕打断点，先按宽度升序，同宽度下再按高度升序排列
  static _setBreakPoints(List<Size> breakPoints) {
    breakPoints.sort((a, b) {
      if (a.width == b.width) {
        return (a.height - b.height > 0 ? 1 : -1);
      }
      return (a.width - b.width) > 0 ? 1 : -1;
    });

    _breakPoints = breakPoints.toList();
  }

  static void _setBreakPointRatio(
      double? startSize, double? endSize, double currentSize) {
    if (startSize == null) {
      _breakPointRatio = 0.0;
      return;
    } else if (endSize == null) {
      _breakPointRatio = 1.0;
      return;
    }
    currentSize =
        currentSize.clamp(min(startSize, endSize), max(startSize, endSize));
    _breakPointRatio = (currentSize - startSize) / (endSize - startSize).abs();
  }

  /// 设置当前屏幕尺寸在打断点列表中的索引，先判断宽度，再判断高度
  /// _breakPointIndex 结果取值遵循以下规则:
  /// 1. 存在与当前屏幕宽度相同的打断点尺寸时，能容纳整个当前屏幕尺寸的最小打断点优先，否则取当
  ///    前宽度下最高的打断点索引
  /// 2. 若无与当前屏幕宽度相同的打断点尺寸情况下，取大于当前屏幕宽度的最小打断点尺寸的索引值
  /// 3. 当有打断点列表时，范围在 [0, breakPoints.length - 1]，否则永远取 -1
  /// 4. 当前屏幕再小也会返回 0，当前屏幕再大时，永远取 breakPoints.length - 1
  ///
  /// 例如：
  /// _breakPointIndex :    0                 1                 2                 3
  /// _breakPoints: [Size(720, 1260), Size(1080, 1440), Size(1080, 1920), Size(1440, 1920)]
  ///                        ⬆                 ⬆                 ⬆                 ⬆
  ///                Size(360, 1260)  Size(960, 1260)   Size(1080, 1441)  Size(1081, 1441)
  ///                Size(720, 720)   Size(1080, 1440)  Size(1080, 2660)  Size(1960, 2660)
  static void _setBreakPointIndex() {
    if (_breakPoints.isEmpty) {
      // 列表为空，取 -1
      _breakPointIndex = -1;
      return;
    }
    for (var i = 0; i < _breakPoints.length; i++) {
      _breakPointIndex = i;
      if (_breakPoints[i].width > _windowSize.width) {
        // 打断点宽度 > 当前屏幕宽度，已符合规则1. ，退出循环
        _setBreakPointRatio(i > 0 ? _breakPoints[i - 1].width : null,
            _breakPoints[i].width, _windowSize.width);
        return;
      } else if (_breakPoints[i].width == _windowSize.width) {
        // 打断点宽度 == 当前屏幕宽度，开始筛选同宽度下最高的打断点
        if ((_breakPoints[i].height >= _windowSize.height ||
            // 下一个打断点不存在
            (i + 1 < _breakPoints.length &&
                // 下一个打断点宽度 > 当前屏幕宽度
                _breakPoints[i + 1].width > _breakPoints[i].width))) {
          // 已找出同宽度下最高的打断点，退出循环
          _setBreakPointRatio(i > 1 ? _breakPoints[i - 1].height : null,
              _breakPoints[i].height, _windowSize.height);
          return;
        } else {
          // 当前打断点无法满足要求，继续测试下一个
          continue;
        }
      } else {
        continue;
      }
    }

    _breakPointRatio = 1;
  }

  static Timer? _timer;

  /// 初始化，建议在 main.dart 的 MaterialApp.builder 中调用
  static init(context, {List<Size>? breakPoints}) {
    frameCb() {
      /// 监听屏幕尺寸变化
      final mediaQueryData = MediaQuery.of(context);
      if (_windowSize != mediaQueryData.size || !_inited) {
        refreshSize(
            height: mediaQueryData.size.height,
            width: mediaQueryData.size.width);
        _setBreakPointIndex();

        for (var callback in _sizeCbs) {
          callback(_windowSize.width, _windowSize.height);
        }
      }
    }

    // 设置屏幕打断点
    if (_breakPoints.isEmpty && breakPoints != null) {
      _setBreakPoints(breakPoints);
    }

    // 首次初始化需要刷新一次屏幕尺寸
    if (!_inited) frameCb();
    _inited = true;

    WidgetsBinding.instance.addPostFrameCallback((d) {
      _timer ??= Timer(updateInterval, () {
        frameCb();
        _timer = null;
        _timer?.cancel();
      });
    });
  }

  /// 添加屏幕尺寸监听
  static void addSizeListener(WindowSizeChangeCallback cb) {
    if (!_sizeCbs.contains(cb)) _sizeCbs.add(cb);
  }

  /// 移除屏幕尺寸监听
  static bool removeSizeListener(WindowSizeChangeCallback cb) {
    return _sizeCbs.remove(cb);
  }

  /// 获取应用尺寸
  static Size get appSize {
    return Size(Adapter._windowSize.width, Adapter._windowSize.height);
  }

  /// 获取应用最小尺寸
  static double get appMinSize {
    return Adapter._windowMinSize;
  }

  static Adapter? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<Adapter>();
  }

  @override
  bool updateShouldNotify(covariant Adapter oldWidget) {
    return true;
  }
}

extension SKAdaptsDouble on double {
  /// 根据窗口高度/设计高度得出缩放后的数值
  double get ad2h {
    return this *
        Adapter._windowSize.height /
        Adapter._designSize.height /
        Adapter.scale;
  }

  /// 根据窗口宽度/设计宽度得出缩放后的数值
  double get ad2w {
    return this *
        Adapter._windowSize.width /
        Adapter._designSize.width /
        Adapter.scale;
  }

  /// 根据窗口高度 * 百分比得出后的数值
  double get vh {
    return this * Adapter._windowSize.height;
  }

  /// 根据窗口宽度 * 百分比得出后的数值
  double get vw {
    return this * Adapter._windowSize.width;
  }
}

extension SKAdaptsInt on int {
  /// 根据窗口高度/设计高度得出缩放后的数值
  double get ad2h {
    return this *
        Adapter._windowSize.height /
        Adapter._designSize.height /
        Adapter.scale;
  }

  /// 根据窗口宽度/设计宽度得出缩放后的数值
  double get ad2w {
    return this *
        Adapter._windowSize.width /
        Adapter._designSize.width /
        Adapter.scale;
  }

  /// 根据窗口高度 * 百分比得出后的数值
  double get vh {
    return this * Adapter._windowSize.height;
  }

  /// 根据窗口宽度 * 百分比得出后的数值
  double get vw {
    return this * Adapter._windowSize.width;
  }
}

extension SKExtenstion<T> on Object {
  /// 根据屏幕尺寸，返回相应打断点下的值，如果设置了，将优先返回 adapt 的计算值而非
  /// [list] 对应 [Adapter._breakPoints] 的顺序
  ///   例如 [Alignment.center, Alignment.centerLeft, null, Alignment.centerRight]
  /// [lerp] 是否使用线性插值，默认为 true，仅对 [num] 类型生效
  T adapt<T>(List<T> list, {bool isLerp = true}) {
    int index = Adapter._breakPointIndex;
    T result;
    if (list.isEmpty) {
      if (T == double && this is num) {
        double value = (this as num).toDouble();
        result = value / Adapter.scale as T;
      } else if (T == int && this is num) {
        int value = (this as num).toInt();
        result = value ~/ Adapter.scale as T;
      } else {
        result = this as T;
      }
      return result;
    }

    if (index >= 0) {
      if (index < list.length) {
        if (T == double || T.toString() == 'double?') {
          if (isLerp && index > 0) {
            result = (lerp<double>(
                  list[index - 1] as double,
                  list[index] as double,
                  Adapter._breakPointRatio,
                ) /
                Adapter.scale) as T;
          } else {
            result = (list[index] as double) / Adapter.scale as T;
          }
        } else if (T == int) {
          result = (list[index] as int) ~/ Adapter.scale as T;
        } else {
          result = list[index];
        }
      } else {
        result = list.last;
      }
    } else {
      result = list[0];
    }

    return result;
  }
}

abstract class AdapterWidget extends StatefulWidget {
  const AdapterWidget({super.key});

  @override
  _AdapterWidgetState createState() => _AdapterWidgetState();

  @protected
  Widget build();
}

class _AdapterWidgetState extends State<AdapterWidget> {
  @override
  void initState() {
    super.initState();
    Adapter.addSizeListener(_updateTree);
  }

  void _updateTree(_, __) {
    if (mounted) {
      setState(() {});
    }
  }

  @override
  void dispose() {
    Adapter.removeSizeListener(_updateTree);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.build();
  }
}

class Adp extends AdapterWidget {
  final Widget Function() builder;

  const Adp(this.builder, {super.key});

  @override
  Widget build() => builder();
}
